#include "log.h"
#include "net.h"
#include "dial.h"

static float _tcp_ping(uint32_t ip, uint16_t port, int timeout, int *err)
{
    int ret;
    struct timeval tmo, s_tm, e_tm;
    struct sockaddr_in peer_addr;
    fd_set wrfds;

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        log_syserr("socket error.");
        *err = TCPPING_ERR_CREAT_SOCK;
        return 0;
    }
    peer_addr.sin_family = AF_INET;
    peer_addr.sin_addr.s_addr = ip;
    peer_addr.sin_port = port;
    setnonblocking(fd);

    gettimeofday(&s_tm, NULL);
    connect(fd, (struct sockaddr*)&peer_addr, sizeof(peer_addr));
    while (1) {
        tmo.tv_sec = timeout ? timeout : 2;
        tmo.tv_usec = 0;
        FD_ZERO(&wrfds);
        FD_SET(fd, &wrfds);
        ret = select(fd + 1, NULL, &wrfds, NULL, &tmo);
        if (ret < 0) {
            log_syserr("tcp ping select error.");
            *err = TCPPING_ERR_SELECT;
            close(fd);
            return 0;
        } else if (ret == 0) {
            close(fd);
            return 0;
        } else {
            ret = connect(fd, (struct sockaddr*)&peer_addr, sizeof(peer_addr));
            if (ret < 0) {
                if (errno == EINPROGRESS || errno == EALREADY)
                    continue;
                else if (errno == EISCONN)
                    break;
                else {
                    log_syserr("tcp ping connect error.");
                    *err = TCPPING_ERR_CONNECT;
                    close(fd);
                    return 0;
                }
            } else {
                break;
            }
        }
    }
    gettimeofday(&e_tm, NULL);
    close(fd);
    return TV2MS(&e_tm) - TV2MS(&s_tm);
}
void* do_tcpping(void* arg)
{
    int i;
    float latency_sum = 0;
    double *platencies = NULL, *platency = NULL, fret;
    dial_arg_t* dial_arg = (dial_arg_t*)arg;
    dial_result_t* dial_result = calloc(1, sizeof(dial_result_t));
    tcpping_arg_t* tcpping_arg = &dial_arg->in.tcpping_arg;
    tcpping_result_t* tcpping_result = &dial_result->out.tcpping_result;

    platency = platencies = calloc(tcpping_arg->counts, sizeof(double));
    for (i = 0; i < tcpping_arg->counts; i++) {
        tcpping_result->snd_count++;
        fret = _tcp_ping(tcpping_arg->ip, tcpping_arg->port, tcpping_arg->timeout, &dial_result->err);
        log_trace("tcp ping seq %d, latency:%.2f", i, fret);
        if (fret == 0) {
            continue;
        }
        latency_sum += fret;
        *(platency++) = fret;
        if (tcpping_result->min_latency == 0) {
            tcpping_result->min_latency = fret;
        } else {
            tcpping_result->min_latency = MIN_VAL(tcpping_result->min_latency, fret);
        }
        tcpping_result->max_latency = MAX_VAL(tcpping_result->max_latency, fret);
        tcpping_result->rcv_count++;
    }
    tcpping_result->latency = tcpping_result->rcv_count ? latency_sum / tcpping_result->rcv_count : 0;
    tcpping_result->lost_percent = (1.0 - (float)tcpping_result->rcv_count / tcpping_result->snd_count) * 100;
    tcpping_result->jitter = cal_jitter(platencies, tcpping_result->rcv_count);
    return (void*)dial_result;
}
void tcpping_result_print(tcpping_result_t* result)
{
    log_trace("latency:%.2f ms", result->latency);
    log_trace("max_latency:%.2f ms, min_latency:%.2f ms", result->max_latency, result->min_latency);
    log_trace("snd_count:%d", result->snd_count);
    log_trace("rcv_count:%d", result->rcv_count);
    log_trace("lost_percent:%.2f", result->lost_percent);
    log_trace("jitter:%.2f ms", result->jitter);
}